package tp3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.Scanner;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;


public class BancoFilmes {
    /* Vetor de filmes */
    private Filme filmes[];
    private int numFilmes;
    private static final int MAX_FILMES = 1000;
    static final String ARQ_BANCO = "dados.txt";

    /* Variaveis utilizadas na sincronizacao */
    private final Lock lockDados = new ReentrantLock();
    private final Lock lockContador = new ReentrantLock();
    private final Condition nenhumLeitor  = lockContador.newCondition();
    private volatile int numLeitores;

    /* Construtor */
    public BancoFilmes(){
        filmes = new Filme[MAX_FILMES];
        numFilmes = 0;
        numLeitores = 0;
    }

    /* Funcoes usadas para inserir leitores e escritores no banco */
    private void adicionarLeitor(){
        lockDados.lock();
        lockContador.lock();
        numLeitores++;
        lockContador.unlock();
        lockDados.unlock();
    }
    private void removerLeitor(){
        lockContador.lock();
        numLeitores--;
        if(numLeitores==0)
            nenhumLeitor.signal();
        lockContador.unlock();
    }
    private void adicionarEscritor(){
        lockDados.lock();
        lockContador.lock();
        while(numLeitores>0){
            try{
                nenhumLeitor.await();
            }catch(InterruptedException e){
                System.err.println("Excecao ao adicionar escritor: " + e.toString());
                e.printStackTrace();
            }
        }
    }
    private void removerEscritor(){
        lockContador.unlock();
        lockDados.unlock();
    }

    /* Carrega o banco de dados na memoria */
    public void carregarBanco(String arq){
        try{
            ObjectInputStream is =
                    new ObjectInputStream(new FileInputStream(arq));
            numFilmes = is.readInt();
            filmes = (Filme [])is.readObject();
        }catch(Exception e){
            System.err.println("Erro ao carregar banco de dados: " + e.toString());
            e.printStackTrace();
        }
    }
    /* Salva o banco de dados na memoria */
    public void salvarBanco(String arq){
        try{
            ObjectOutputStream os =
                    new ObjectOutputStream(new FileOutputStream(arq, false));
            os.writeInt(numFilmes);
            os.writeObject(filmes);
        }catch(Exception e){
            System.err.println("Erro ao salvar banco de dados: " + e.toString());
            e.printStackTrace();
        }
    }

    /* Insere novo filme no banco de dados ja carregado na memoria
       Recebe um scanner para uma stream de entrada */
    public void inserirFilme(Scanner scan){
        Filme filme = new Filme();
        System.out.println("Digite o titulo do filme: ");
        filme.setTitulo(scan.next());
        System.out.println("Digite o horario do filme: ");
        filme.setHorario(scan.next());
        System.out.println("Digite a sala do filme: ");
        filme.setSala(scan.nextInt());
        System.out.println("Digite a sinopse do filme: ");
        filme.setSinopse(scan.next());
        filme.setId(numFilmes);
        filmes[numFilmes] = filme;
        numFilmes++;
    }

    /* Retorna um vetor de strings com todos os titulos */
    public String[] recuperarTitulos(){
        adicionarLeitor();
        String[] titulos = new String[numFilmes];
        int i;
        for(i=0; i<numFilmes; i++)
            titulos[i] = filmes[i].getTitulo();
        removerLeitor();
        return titulos;
    }
    /* Retorna a sinopse de um filme dado seu id */
    public String recuperarSinopseFilme(int id){
        adicionarLeitor();
        String sinopse = filmes[id].getSinopse();
        removerLeitor();
        return sinopse;
    }
    /* Retorna todas as informacoes de um filme dado seu id */
    public Filme recuperarInformacoesFilme(int id){
        adicionarLeitor();
        Filme filme = new Filme(filmes[id]);
        removerLeitor();
        return filme;
    }
    /* Retorna todas as informacoes de todos os filmes */
    public Filme[] recuperarTudo(){
        adicionarLeitor();
        Filme[] tudo = new Filme[numFilmes];
        for(int i=0; i<numFilmes; i++)
            tudo[i] = new Filme(filmes[i]);
        removerLeitor();
        return tudo;
    }
    /* Da nota para um filme */
    public int darNotaFilme(int id, int nota){
        adicionarEscritor();
        int novaNota = filmes[id].modificaNota(nota);
        removerEscritor();
        return novaNota;
    }
    /* Retorna a nota e o numero de clientes que deram nota a um filme */
    public Pair<Integer, Integer> recuperarNotaFilme(int id){
        adicionarLeitor();
        Pair<Integer, Integer> p = new Pair<Integer, Integer>(filmes[id].getNota(), filmes[id].getNumClientes());
        removerLeitor();
        return p;
    }

    /* Usada apenas para popular o banco */
    public static void main(String args[]){
        BancoFilmes bancoFilmes = new BancoFilmes();
        Scanner scan;
        String arqEntrada = "populacao_bd.txt";
        int total = 0;
        try{
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(arqEntrada)));
            scan = new Scanner(br).useDelimiter("\n");
            int opt = scan.nextInt();
            while(opt==1){
                bancoFilmes.inserirFilme(scan);
                total++;
                opt = scan.nextInt();
            }
            scan.close();
            System.out.println("Leu "+total+" filmes.");
            bancoFilmes.salvarBanco(BancoFilmes.ARQ_BANCO);
        }catch(Exception e){
            System.err.println("Erro ao carregar arquivo de entrada: " + e.toString());
            e.printStackTrace();
        }
    }
}
